package gui.views;

import gui.text.ChildPane;
import gui.text.MathDocument;
import gui.text.MathKit;
import gui.text.RootPane;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collection;
import java.util.LinkedList;
import java.util.concurrent.ExecutionException;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.JViewport;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.Document;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;

import libraries.Library;
import parsetree.ElementFactory;
import util.TrieMap;

import com.google.common.base.Joiner;


/**
 * Base for the ScratchPad GUI.
 * See http://www.ctan.org/pkg/asana-math
 * 
 * @author Joshua Lockerman
 * 
 */
public class Main extends JFrame {
    
    /**
     * 
     */
    private static final long serialVersionUID = -768512539207469236L;
    
    private static final String TITLE = "ScratchPad";
    public static final String fontLocation = "fonts/Asana-Math.ttf";
    private static final float defaultSize = 14f;
    
    private JMenuBar menuBar;
    private final JTabbedPane mainPane;
    private final Font mathFont;
    private transient int newCount;
    
    private final Collection<metagrammar.Type> typeLibrary;
    private final TrieMap<ElementFactory> operatorLibrary;
    
    /**
     * Default constructor for Main.
     * 
     * @throws IOException
     * @throws FontFormatException
     */
    public Main() throws FontFormatException, IOException
    {
        super(TITLE);
        newCount = 0;
        
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        catch(UnsupportedLookAndFeelException f) {
            // TODO
        }
        catch(ClassNotFoundException f) {
            // TODO
        }
        catch(InstantiationException f) {
            // TODO
        }
        catch(IllegalAccessException f) {
            // TODO
        }
        
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setLocationByPlatform(true);
        setLayout(new BorderLayout());
        
        // mathFont = Font.createFont(Font.TRUETYPE_FONT, new File(fontLocation)).deriveFont(
        // defaultSize);
        
        createMenuBar();
        
        mathFont = Font.decode("Cambria Math").deriveFont(defaultSize);
        
        mainPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
        add(mainPane, BorderLayout.CENTER);
        
        typeLibrary = new LinkedList<metagrammar.Type>();
        
        operatorLibrary = new TrieMap<ElementFactory>();
        
        // TODO
        Preferences prefs = Preferences.userNodeForPackage(Main.class);
        Toolkit kit = Toolkit.getDefaultToolkit();
        Dimension screenSize = kit.getScreenSize();
        int width = prefs.getInt("width", screenSize.width / 2);
        int height = prefs.getInt("height", screenSize.height / 2);
        setSize(width, height);
        // END TODO
    }
    
    private void createMenuBar()
    {
        menuBar = new JMenuBar();
        
        JMenu fileMenu = new JMenu("File");
        JMenuItem createNew = new JMenuItem("New");
        createNew.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                newDocument();
            }
        });
        
        fileMenu.add(createNew);
        
        // TODO
        JMenuItem saveItem = new JMenuItem("Save");
        saveItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                saveAs();
            }
        });
        fileMenu.add(saveItem);
        
        JMenuItem loadItem = new JMenuItem("Load");
        loadItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                load();
            }
        });
        fileMenu.add(loadItem);
        //End TODO
        
        menuBar.add(fileMenu);
        
        JMenu libraryMenu = new JMenu("Library");
        JMenuItem createType = new JMenuItem("Create Type");
        createType.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                openCreateTypeView();
            }
        });
        libraryMenu.add(createType);
        
        JMenuItem createOperator = new JMenuItem("Create Operator");
        createOperator.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                openCreateOperatorView();
            }
        });
        libraryMenu.add(createOperator);
        
        JMenuItem createSpec = new JMenuItem("Create Spec");
        createSpec.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                openCreateSpecView();
            }
        });
        libraryMenu.add(createSpec);
        
        
        JMenuItem addRule = new JMenuItem("Add Axiomatic Transform");
        addRule.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                openAddAxiomaticTransformView();
            }
        });
        libraryMenu.add(addRule);
        
        JMenuItem createLibrary = new JMenuItem("Create Library");
        createLibrary.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                openCreateLibraryView();
            }
        });
        libraryMenu.add(createLibrary);
        
        menuBar.add(libraryMenu);
        
        JMenu insertMenu = new JMenu("Insert");
        
        JMenuItem addTextField = new JMenuItem("Insert Text Area");
        addTextField.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                Component c = mainPane.getSelectedComponent();
                if(c != null) {
                    JTextPane editor = (JTextPane) ((JViewport) ((JScrollPane) c).getComponent(0))
                            .getComponent(0);
                    editor.insertComponent(new ChildPane(editor));
                }
            }
        });
        insertMenu.add(addTextField);
        menuBar.add(insertMenu);
        
        JMenu documentMenu = new JMenu("Document");
        
        Action verifyAction = new AbstractAction("Verify") {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                verifyDocument();
            }
        };
        
        JMenuItem verify = new JMenuItem(verifyAction);
        
        documentMenu.add(verify);
        menuBar.add(documentMenu);
        
        setJMenuBar(menuBar);
    }
    
    /**
     * Creates a new document and adds it to the main tabbed pane.
     * 
     */
    private void newDocument()
    {
        MathDocument document;
        try {
            Library lib = SelectLibraryDialog.show(this);
            if(lib != null) {
                document = new MathDocument(lib);
                addTab(document);
            }
        }
        catch(ExecutionException e) {
            JPanel panel = new JPanel();
            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
            panel.add(new JLabel("The Library you requested could not be loaded because:"));
            panel.add(new JLabel(e.toString()));
            JTextArea textArea = new JTextArea(Joiner.on("\n").join(e.getStackTrace()), 10, 10);
            Throwable t = e;
            if(t.getCause() != null) {
                t = t.getCause();
                textArea.append("\n" + Joiner.on("\n").join(t.getStackTrace()));
            }
            JScrollPane sp = new JScrollPane(textArea);
            panel.add(sp);
            JOptionPane.showMessageDialog(this, panel, "Document Creation Error",
                    JOptionPane.ERROR_MESSAGE);
        }
        catch(Exception e) {
            JPanel panel = new JPanel();
            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
            panel.add(new JLabel("Your document could not be created because because:"));
            panel.add(new JLabel(e.toString()));
            JTextArea textArea = new JTextArea(Joiner.on("\n").join(e.getStackTrace()), 10, 10);
            JScrollPane sp = new JScrollPane(textArea);
            panel.add(sp);
            JOptionPane.showMessageDialog(this, panel, "Document Creation Error",
                    JOptionPane.ERROR_MESSAGE);
        }
        
    }
    
    /**
     * @param document
     */
    private void addTab(MathDocument document)
    {
        final RootPane editor = new RootPane();
        editor.setEditorKit(new MathKit());
        editor.setFont(mathFont);
        editor.setDocument(document);
        
        SimpleAttributeSet center = new SimpleAttributeSet();
        StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
        document.setParagraphAttributes(0, document.getLength(), center, false);
        
        final JScrollPane scroll = new JScrollPane(editor,
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        
        
        final JLabel label = new JLabel();
        
        document.addTitleChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt)
            {
                if(evt.getNewValue() instanceof String) {
                    label.setText((String) evt.getNewValue());
                }
            }
        });
        
        String title = document.getTitle();
        if(title == null || title.length() == 0) {
            document.setTitle("Untitled" + ((newCount > 0) ? " " + newCount : ""));
            newCount++ ;
        }
        else {
            label.setText(title);
        }
        
        editor.addCaretListener(new CaretListener() {
            @Override
            public void caretUpdate(CaretEvent e)
            {
                editor.caretUpdate(e);
            }
        });
        
        mainPane.add(scroll);
        mainPane.setTabComponentAt(mainPane.getTabCount() - 1, label);
    }
    
    private void openAddAxiomaticTransformView()
    {
        new AddAxiomaticTransformView().setVisible(true);
    }
    
    private void openCreateTypeView()
    {
        CreateTypeView create = new CreateTypeView();
        create.setVisible(true);
    }
    
    private void openCreateOperatorView()
    {
        CreateOperatorView create = new CreateOperatorView();
        create.setVisible(true);
    }
    
    private void openCreateSpecView()
    {
        CreateSpecView create = new CreateSpecView();
        create.setVisible(true);
    }
    
    private void openCreateLibraryView()
    {
        new CreateLibraryView().setVisible(true);
    }
    
    private void verifyDocument()
    {
        // TODO
        Document doc = getSelectedDocument();
        if(doc instanceof MathDocument) {
            if(((MathDocument) doc).verify()) {
                JOptionPane.showMessageDialog(mainPane, "Verification Suceeded.");
            }
            else {
                JOptionPane.showMessageDialog(mainPane, "Verification failed.");
            }
        }
        else {
            JOptionPane.showMessageDialog(mainPane, "Must have a valid math document to verify.",
                    "Verification Error", JOptionPane.ERROR_MESSAGE);
        }
    }
    
    /**
     * @param c
     * @return
     */
    private Document getSelectedDocument()
    {
        Component c = mainPane.getSelectedComponent();
        Document doc = null;
        if(c != null) {
            JTextPane editor = (JTextPane) ((JViewport) ((JScrollPane) c).getComponent(0))
                    .getComponent(0);
            doc = editor.getDocument();
        }
        return doc;
    }
    
    private void saveAs()
    {
        Document doc = getSelectedDocument();
        if(doc instanceof MathDocument) {
            JFileChooser fileChooser = new JFileChooser();
            int returnVal = fileChooser.showSaveDialog(this);
            if(returnVal == JFileChooser.APPROVE_OPTION) {
                File file = fileChooser.getSelectedFile();
                ObjectOutputStream oos = null;
                try {
                    oos = new ObjectOutputStream(new BufferedOutputStream(
                            new FileOutputStream(file)));
                    oos.writeObject(doc);
                    ((MathDocument) doc).setTitle(file.getName());
                }
                catch(FileNotFoundException e) {
                    JOptionPane.showMessageDialog(this, e.getLocalizedMessage(),
                            "Inavalid File Name.", JOptionPane.ERROR_MESSAGE);
                }
                catch(IOException e) {
                    JOptionPane.showMessageDialog(this, e.getLocalizedMessage(), "I/O Error.",
                            JOptionPane.ERROR_MESSAGE);
                }
                finally {
                    try {
                        if(oos != null) {
                            oos.close();
                        }
                    }
                    catch(IOException e) {
                        JOptionPane.showMessageDialog(this, e.getLocalizedMessage(), "I/O Error.",
                                JOptionPane.ERROR_MESSAGE);
                    }
                }
            }
        }
    }
    
    private void load()
    {
        JFileChooser fileChooser = new JFileChooser();
        int returnVal = fileChooser.showOpenDialog(this);
        if(returnVal == JFileChooser.APPROVE_OPTION) {
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(
                        fileChooser.getSelectedFile())));
                MathDocument document = (MathDocument) ois.readObject();
                addTab(document);
            }
            catch(FileNotFoundException e) {
                JOptionPane.showMessageDialog(this, e.getLocalizedMessage(), "Inavalid File Name.",
                        JOptionPane.ERROR_MESSAGE);
            }
            catch(IOException e) {
                JOptionPane.showMessageDialog(this, e.getLocalizedMessage(), "I/O Error.",
                        JOptionPane.ERROR_MESSAGE);
            }
            catch(ClassNotFoundException e) {
                JOptionPane.showMessageDialog(this, e.getLocalizedMessage(), "Internal Error.",
                        JOptionPane.ERROR_MESSAGE);
            }
            finally {
                try {
                    if(ois != null) {
                        ois.close();
                    }
                }
                catch(IOException e) {
                    JOptionPane.showMessageDialog(this, e.getLocalizedMessage(), "I/O Error.",
                            JOptionPane.ERROR_MESSAGE);
                }
            }
        }
    }
    
    @Override
    public void dispose()
    {
        Preferences prefs = Preferences.userNodeForPackage(Main.class);
        prefs.putInt("height", getHeight());
        prefs.putInt("width", getWidth());
        super.dispose();
    }
}
